package com.nulldev.util;

import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.nulldev.util.io.FileUtil;
import com.nulldev.util.java.ReflectionUtils;
import com.nulldev.util.java.Variables;
import com.nulldev.util.java.var;
import com.nulldev.util.java.ReflectionUtils.ClassUtil;
import com.nulldev.util.java.date.DateUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.math.MathUtil;
import com.nulldev.util.math.lNumber;
import com.nulldev.util.other.ByteUtils;

public class FinalBypass<Obj> implements Serializable {
	
	@Deprecated
	/**
	 * This should be removed.
	 */
	private final ArrayList<Obj> temp = new ArrayList<Obj>(1);
	private final Logger log = LoggerFactory.getLogger(true, true);
	private boolean lock;
	private Obj obj;
	
	public FinalBypass(Obj object) {
		this.obj = object;
	}
	
	public static <Obj> FinalBypass<Obj> createBypass(final Class<Obj> c) {
		if (c == null) {
			throw new UnsupportedOperationException("createBypass(Obj<Obj>) -> Object was null!");
		}
		if (c == Boolean.class) {
			return new FinalBypass(false);
		}else if (c == String.class) {
			return new FinalBypass("");
		}else if (c == Integer.class) {
			return new FinalBypass((int) 0);
		}else if (c == Double.class) {
			return new FinalBypass(0d);
		}else if (c == Float.class) {
			return new FinalBypass(0f);
		}else if (c == Short.class) {
			return new FinalBypass((short) 0);
		}else if (c == Long.class) {
			return new FinalBypass(0L);
		}else if (c == lNumber.class) {
			return new FinalBypass(lNumber.nil());
		}else if (c == BigInteger.class) {
			return new FinalBypass(new BigInteger("0"));
		}else if (c == BigDecimal.class) {
			return new FinalBypass(new BigDecimal(0));
		}else if (c == Character.class) {
			return new FinalBypass('\u0000');
		}else if (c == File.class) {
			return new FinalBypass(FileUtil.safeCreate(FileUtil.HOME_DIRECTORY));
		}else if (c == Date.class) {
			return new FinalBypass(DateUtil.get());
		}else if (c == StringBuilder.class) {
			return new FinalBypass(new StringBuilder());
		}else {
			try {
				return new FinalBypass<Obj>((Obj) c.newInstance());
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}

	public Obj getObject() {
		if (this.obj == null) {
			this.log.warn("The object is currently null! [Called by " + Thread.currentThread().getStackTrace()[2].getClassName() + " @ " + Thread.currentThread().getStackTrace()[2].getMethodName()+ "]");
			//System.out.println("java.warn(\"Object is null.\") | Called by " + Thread.currentThread().getStackTrace()[2].getClassName() + " @ " + Thread.currentThread().getStackTrace()[2].getMethodName());
		}
		return obj;
	}
	
	public void println() {
		System.out.println(this.toString());
	}

	public void setObject(Obj obj) {
		if (!this.lock) {
			this.obj = obj;
		}else {
		}
	}
	
	public void setNull() {
		if (!this.lock) {
			this.obj = null;
		}else {
		}
	}

	@Override
	public String toString() {
		if (this.obj.getClass().isAssignableFrom(String.class)) {
			return (String) this.obj;
		}else {
			return "Object from " + this.obj.getClass().getName() + ".class | Value '" + this.obj + "'";
		}
	}

	public void add(final Obj i) {
		if (this.obj.getClass().isAssignableFrom(Integer.class)) {
			final int te = (Integer) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Integer") {
				final int out = te + (Integer) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Float.class)) {
			final float te = (Float) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Float") {
				final float out = te + (Float) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Double.class)) {
			final double te = (Double) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Double") {
				final double out = te + (Double) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Long.class)) {
			final long te = (Long) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Long") {
				final long out = te + (Long) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Short.class)) {
			final short te = (Short) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Short") {
				final short out = (short) (te + (Short) i);
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(byte[].class)) {
			final byte[] te = (byte[]) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "[B") {
				final byte[] out = (byte[]) ByteUtils.concatenateByteArrays(te, (byte[]) i);
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a byte[]!");
			}
		}else if (this.obj.getClass().isAssignableFrom(lNumber.class)) { 
			final lNumber te = (lNumber) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "com.nulldev.util.math.lNumber") {
				final lNumber nw = te.add((long) i);
				ReflectionUtils.setField(this, "obj", nw);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a lNumber!");
			}
		}else {
			throw new UnsupportedOperationException("Main Object is not a number! [Class: " + this.obj.getClass().getSimpleName() + "]");
		}
	}

	public void minus(final Obj i) {
		if (this.obj.getClass().isAssignableFrom(Integer.class)) {
			final int te = (Integer) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Integer") {
				final int out = te - (Integer) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Float.class)) {
			final float te = (Float) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Float") {
				final float out = te - (Float) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Double.class)) {
			final double te = (Double) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Double") {
				final double out = te - (Double) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Long.class)) {
			final long te = (Long) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Long") {
				final long out = te - (Long) i;
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(Short.class)) {
			final short te = (Short) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "java.lang.Short") {
				final short out = (short) (te - (Short) i);
				ReflectionUtils.setField(this, "obj", out);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a number!");
			}
		}else if (this.obj.getClass().isAssignableFrom(lNumber.class)) { 
			final lNumber te = (lNumber) this.obj;
			final String vs = i.getClass().getName();
			if (vs == "com.nulldev.util.math.lNumber") {
				final lNumber nw = te.minus((long) i);
				ReflectionUtils.setField(this, "obj", nw);
			}else {
				throw new UnsupportedOperationException("Value 'i' is not a lNumber!");
			}
		}else {
			throw new UnsupportedOperationException("Main Object is not a number!");
		}
	}

	public var<Obj> toVar() {
		return Variables.createVar(this.obj);
	}

	public boolean isLocked() {
		return this.lock;
	}

	public void lock() {
		if (this.lock) {
			return;
		}
		this.lock = true;
	}

	public void unlock() {
		if (!this.lock) {
			return;
		}
		this.lock = false;
	}

	public int LongToInt() {
		if (this.obj instanceof Long) {
			return MathUtil.safeLongToInt((Long) this.obj);
		}else {
			throw new UnsupportedOperationException("Main Object is not a number!");
		}
	}
	
	@Override
	public boolean equals(final Object obj) {
		return this.obj.getClass().getName().equals(obj.getClass().getName()) && this.obj == obj;
	}

	public boolean notNull() {
		return (this.obj != null);
	}

	public void setObject(final FinalBypass<Obj> final_bypass) {
		this.setObject((Obj) final_bypass.getObject());
	}

	public boolean isNull() {
		return !this.notNull();
	}
}
